package com.ld.shieldsb.common.core.util;

import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.X509TrustManager;

import org.jsoup.Connection;
import org.jsoup.Connection.Method;
import org.jsoup.Connection.Response;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Whitelist;

import com.alibaba.fastjson.JSONObject;

import lombok.extern.slf4j.Slf4j;

/**
 * 描述: 过滤 HTML 标签中 XSS 代码
 */
@Slf4j
public class JsoupUtil {
    public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36";
    public static final int TIMEOUT_MILLIS = 5 * 1000;

    private static String BASE_URI = "";
    /**
     * 使用自带的 none 白名单 只保留标签内文本内容,用于普通输入框输入,可在具体项目中覆盖规则
     * 
     */
    private static final Whitelist WHITELIST_NONE = Whitelist.none();
    /**
     * 属性 relaxed 白名单 用于文本编辑器输入允许的便签有a,b,blockquote,br,caption,cite, code,col,colgroup,dd,div,dl,dt, em,h1,h2,h3,h4,h5,h6,i,img,li,
     * ol,p,pre,q,small,span,strike,strong, sub,sup,table,tbody,td,tfoot,th,thead,tr,u,ul
     * 
     */
    private static final Whitelist WHITELIST = Whitelist.relaxed();

    /** 配置过滤化参数, 不对代码进行格式化 */
    private static final Document.OutputSettings OUTPUT_SETTINGS = new Document.OutputSettings().prettyPrint(false);
    /** 配置过滤化参数, 对代码进行格式化 */
    private static final Document.OutputSettings OUTPUT_SETTINGS_PRETTY = new Document.OutputSettings().prettyPrint(true);
    static {
        // 富文本编辑时一些样式是使用 style 来进行实现的
        // 比如红色字体 style="color:red;"
        // 所以需要给所有标签添加 style 属性
        WHITELIST.addAttributes(":all", "style");
        WHITELIST.addAttributes("pre", "class"); // 代码语言
        WHITELIST.addAttributes("a", "target", "_blank"); // 新窗口打开
        WHITELIST.addAttributes("video", "class", "controls", "preload", "width", "height", "src"); // 视频
        WHITELIST.addAttributes("source", "src", "type"); // 视频
        WHITELIST.addAttributes("embed", "class", "pluginspage", "src", "width", "height", "wmode", "play", "loop", "menu",
                "allowscriptaccess", "allowfullscreen"); // 视频
        WHITELIST.addTags("video", "embed");
        // 移除Protocols设置否则写相对路径不能通过
        WHITELIST.removeProtocols("a", "href", "ftp", "http", "https", "mailto"); // 不验证，允许写相对路径
        WHITELIST.removeProtocols("img", "src", "http", "https"); // 不验证，允许写相对路径

        trustEveryone();
    }

    /**
     * 过滤html标签(默认不格式化)
     * 
     * @Title clean
     * @author 吕凯
     * @date 2018年7月2日 上午8:58:11
     * @param content
     *            过滤内容
     * @return String
     */
    public static String clean(String content) {
        return clean(content, false);
    }

    /**
     * 过滤html标签
     * 
     * @Title clean
     * @author 吕凯
     * @date 2018年7月2日 上午8:57:30
     * @param content
     *            过滤内容
     * @param pretty
     *            是否格式化
     * @return String
     */
    public static String clean(String content, boolean pretty) {
        // Jsoup.clean() 方法返回的代码已经被进行格式化, 在标签及标签内容之间添加了 \n 回车符, 如果不需要的话,
        // 可以使用 Jsoup.clean(testHtml, "", whitelist, new Document.OutputSettings().prettyPrint(false)); 进行过滤
        if (pretty) {
            return Jsoup.clean(content, BASE_URI, WHITELIST, OUTPUT_SETTINGS_PRETTY);
        }
        return Jsoup.clean(content, BASE_URI, WHITELIST, OUTPUT_SETTINGS);
    }

    /**
     * 清除html标签，用于普通input输入过滤（里面的html标签进行了转移，如>转义为&gt;）
     * 
     * @Title cleanAllHtml
     * @author 吕凯
     * @date 2018年7月2日 上午9:17:44
     * @param content
     * @return String
     */
    public static String cleanAllHtml(String content) {
        return Jsoup.clean(content, BASE_URI, WHITELIST_NONE, OUTPUT_SETTINGS);
    }

    /**
     * 去除html标签，html特殊字符转换为正常显示的代码，如&gt;显示为>
     * 
     * @Title stripHtml
     * @author 吕凯
     * @date 2018年11月21日 上午10:13:21
     * @param htmlStr
     * @return String
     */
    public static String stripHtml(String htmlStr) {
        return Jsoup.parse(htmlStr).text();
    }

    // ========================= 远程内容获取 begin =====================
    /**
     * 获取远程地址的内容(html字符串)
     * 
     * @Title getRemoteContent
     * @author 吕凯
     * @date 2018年11月17日 上午10:26:06
     * @param url
     * @return String
     */
    public static String getRemoteURLContent(String url) {
        return getRemoteURLContent(url, USER_AGENT, TIMEOUT_MILLIS);
    }

    /**
     * 
     * 获取远程地址的内容(html字符串)
     * 
     * @Title getRemoteURLContent
     * @author 吕凯
     * @date 2018年11月17日 上午10:38:44
     * @param url
     * @param userAgent
     * @param timeoutMillis
     *            超时时间，单位毫秒
     * @return String
     */
    public static String getRemoteURLContent(String url, String userAgent, int timeoutMillis) {
        return getRemoteURLContent(url, userAgent, timeoutMillis, null, "get");
    }

    public static String getRemoteURLContent(String url, Map<String, String> data, String method) {
        return getRemoteURLContent(url, USER_AGENT, TIMEOUT_MILLIS, data, method);
    }

    public static String getRemoteURLContent(String url, String userAgent, int timeoutMillis, String method) {
        return getRemoteURLContent(url, userAgent, timeoutMillis, null, method);
    }

    public static String getRemoteURLContent(String url, String userAgent, int timeoutMillis, Map<String, String> data, String method) {
        if (method == null) {
            method = "get";
        }
        Document doc = getRemoteURLDocument(url, userAgent, timeoutMillis, data,
                method.equalsIgnoreCase("post") ? Method.POST : Method.GET);
        if (doc != null) {
            return doc.toString();
        }
        return null;
    }

    /**
     * 获取远程rul的Document对象（jsoup包内对象，用于自己解析）
     * 
     * @Title getRemoteURLDocument
     * @author 吕凯
     * @date 2018年11月17日 上午10:48:13
     * @param url
     * @param userAgent
     * @param timeoutMillis
     *            超时时间，单位毫秒
     * @return Document
     */
    public static Document getRemoteURLDocument(String url, String userAgent, int timeoutMillis) {
        return getRemoteURLDocument(url, userAgent, timeoutMillis, null);
    }

    public static Document getRemoteURLDocument(String url, String userAgent, int timeoutMillis, Map<String, String> data) {
        return getRemoteURLDocument(url, userAgent, timeoutMillis, data, Method.GET);
    }

    public static Document getRemoteURLDocument(String url, Map<String, String> data) {
        return getRemoteURLDocument(url, USER_AGENT, TIMEOUT_MILLIS, data, Method.GET);
    }

    public static Document getRemoteURLDocument(String url, int timeoutMillis, Map<String, String> data) {
        return getRemoteURLDocument(url, USER_AGENT, timeoutMillis, data, Method.GET);
    }

    public static Document getRemoteURLDocument(String url, String userAgent, int timeoutMillis, Map<String, String> data, Method method) {
        return getRemoteURLDocument(url, userAgent, timeoutMillis, null, data, method);
    }

    public static Document getRemoteURLDocument(String url, Map<String, String> headers, Map<String, String> data) {
        return getRemoteURLDocument(url, headers, data, Method.GET);
    }

    public static Document getRemoteURLDocument(String url, Map<String, String> headers, Map<String, String> data, Method method) {
        return getRemoteURLDocument(url, USER_AGENT, TIMEOUT_MILLIS, headers, data, method);
    }

    public static Document getRemoteURLDocument(String url, String userAgent, int timeoutMillis, Map<String, String> headers,
            Map<String, String> data, Method method) {
        try {
//            if (url.startsWith("https")) {
//                trustEveryone();
//            }
            Connection conn = Jsoup.connect(url).userAgent(userAgent).timeout(timeoutMillis);
            if (method != null) { // 默认为get方法
                conn = conn.method(method);
            }
            if (headers != null && !headers.isEmpty()) {
                conn = conn.headers(headers);
            }
            if (data != null && !data.isEmpty()) {
                conn = conn.data(data);
            }
            conn.request().ignoreHttpErrors(true); // 忽略错误继续执行status < 200 || status >= 400的为错误
            Document doc = null;
            Response response = conn.execute();
            doc = response.parse();
            return doc;
        } catch (Exception e) {
            log.error("获取远程内容出错！", e);
            return null;
        }
    }

    /**
     * 信任任何站点，实现https页面的正常访问
     * 
     */

    public static void trustEveryone() {
        try {
            HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });

            SSLContext context = SSLContext.getInstance("TLS");
            context.init(null, new X509TrustManager[] { new X509TrustManager() {
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }

                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }

                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            } }, new SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
        } catch (Exception e) {
            // e.printStackTrace();
        }
    }

    /**
     * 获取json格式的返回结果
     * 
     * @Title getRemoteURLJson
     * @author 吕凯
     * @date 2018年12月29日 下午2:54:58
     * @param url
     * @return JSONObject
     */
    public static JSONObject getRemoteURLJson(String url) {
        return getRemoteURLJson(url, USER_AGENT, TIMEOUT_MILLIS);
    }

    /**
     * 获取json格式的返回结果
     * 
     * @Title getRemoteURLJson
     * @author 吕凯
     * @date 2019年6月17日 下午12:03:01
     * @param url
     * @param timeoutMillis
     *            超时秒数
     * @return JSONObject
     */
    public static JSONObject getRemoteURLJson(String url, int timeoutSeconds) {
        return getRemoteURLJson(url, USER_AGENT, timeoutSeconds * 1000);
    }

    /**
     * 获取json格式的返回结果
     * 
     * @Title getRemoteURLJson
     * @author 吕凯
     * @date 2018年12月29日 下午2:54:44
     * @param url
     * @param userAgent
     * @param timeoutMillis
     * @return JSONObject
     */
    public static JSONObject getRemoteURLJson(String url, String userAgent, int timeoutMillis) {
        return getRemoteURLJson(url, userAgent, timeoutMillis, null, null);
    }

    public static JSONObject getRemoteURLJson(String url, String userAgent, int timeoutMillis, Method method) {
        return getRemoteURLJson(url, userAgent, timeoutMillis, null, method);
    }

    public static JSONObject getRemoteURLJson(String url, String userAgent, int timeoutMillis, Map<String, String> data) {
        return getRemoteURLJson(url, userAgent, timeoutMillis, data, null);
    }

    public static JSONObject getRemoteURLJson(String url, int timeoutMillis, Map<String, String> data) {
        return getRemoteURLJson(url, USER_AGENT, timeoutMillis, data, null);
    }

    public static JSONObject getRemoteURLJson(String url, Map<String, String> data) {
        return getRemoteURLJson(url, USER_AGENT, TIMEOUT_MILLIS, data, null);
    }

    public static JSONObject getRemoteURLJson(String url, Map<String, String> headers, Map<String, String> data) {
        return getRemoteURLJson(url, USER_AGENT, TIMEOUT_MILLIS, headers, data, null);
    }

    public static JSONObject getRemoteURLJson(String url, String userAgent, int timeoutMillis, Map<String, String> data, Method method) {
        return getRemoteURLJson(url, userAgent, timeoutMillis, null, data, method);
    }

    public static JSONObject getRemoteURLJson(String url, String userAgent, int timeoutMillis, Map<String, String> headers,
            Map<String, String> data, Method method) {
        try {
//            if (url.startsWith("https")) {
//                trustEveryone();
//            }
            Connection conn = Jsoup.connect(url).userAgent(userAgent).timeout(timeoutMillis).ignoreContentType(true);
            if (method != null) { // 默认为get方法
                conn = conn.method(method);
            }
            if (headers != null && !headers.isEmpty()) {
                conn = conn.headers(headers);
            }
            if (data != null && !data.isEmpty()) {
                conn = conn.data(data);
            }
            Response response = conn.execute();
            String body = response.body();
            JSONObject json = JSONObject.parseObject(body);
            return json;
        } catch (Exception e) {
            log.error("获取远程内容出错！", e);
            return null;
        }
    }

    /**
     * 发送json数据，post方式
     * 
     * @Title sendJson
     * @author 吕凯
     * @date 2018年12月29日 下午3:19:38
     * @param data
     * @param url
     * @param userAgent
     * @param timeoutMillis
     * @return JSONObject
     */
    public static JSONObject sendJson(Map<String, Object> data, String url, String userAgent, int timeoutMillis) {
        try {
            Response response = Jsoup.connect(url).userAgent(userAgent).timeout(timeoutMillis).method(Method.POST)
                    .requestBody(JSONObject.toJSONString(data)).header("Content-Type", "application/json").ignoreContentType(true)
                    .execute();
            String body = response.body();
            JSONObject json = JSONObject.parseObject(body);
            return json;
        } catch (Exception e) {
            log.error("获取远程内容出错！", e);
            return null;
        }
    }

    // ========================= 远程内容获取 end =====================

    public static void main(String[] args) {
        String testHtml = "<div class='div'style='height: 100px;'>div 标签的内容 </div><p class='div'style='width: 50px;'>p 标签的内容 </p>";
        System.out.println(JsoupUtil.clean(testHtml));
        System.out.println(JsoupUtil.cleanAllHtml(testHtml));
    }

    public static String getBaseUri() {
        return BASE_URI;
    }

    public static void setBaseUri(String baseUri) {
        BASE_URI = baseUri;
    }

    public static Whitelist getWhitelistNone() {
        return WHITELIST_NONE;
    }

    public static Whitelist getWhitelist() {
        return WHITELIST;
    }

}
